<?php
/**
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* 
* @copyright Copyright (c) 2008-2009 Ne8, LLC <josh.ribakoff@gmail.com>
* @license New BSD License
* @version 0.1
* @category Shuffler
* @package Shuffler_Mapper
* 
* @todo implement association table mapping
*/
class Shuffler_Mapper
{
    
    /**
    * @var Shuffler_Mapper_Adapter for interfacing the data store
    */
    protected $adapter;
    
    /**
    * @var string name of table for this mapper
    */
    protected $tableName;
    protected $schema;
    
    /**
    * @var name of model this mapper maps for
    */
    protected $modelName;
                                                                   
    /**
    * @var Shuffler_Mapper_IdentityMap
    */
    public $identityMap;
    
    /**
    * @var string primaryKey
    */
    protected $primaryKey;
    
    /**
    * @var array of Shuffler_Mapper_Mappings to use as strategies for mapping fields
    */
    protected $mappings = array();
                            
    /**
    * Create the mapper
    * 
    * sets the tablename automatically based on the model name, which can be overriden in the init() routine
    * 
    * @param string name of model this mapper maps for
    * @param mixed database adapter
    */
    public function __construct( $modelName, $adapter )
    {
        $this->identityMap = new Shuffler_Mapper_IdentityMap();
        $this->adapter = $adapter;
        $this->modelName = $modelName;       
        $this->tableName = strtolower( $modelName );
        $this->init();                                    
    }
    
    /**
    * @param string field
    * @return Shuffler_Mapper
    */
    public function addPrimaryKey( $field = null )
    {
        if( empty( $field ) )
        {
            $field = 'id';
        }

        $this->primaryKey = $field;
        $this->addEmbedded( $field, array( 'immutable' => true ) );
        return $this;
    }
    
    /**
    * get an instance of a data mapper
    * 
    * @param name of model to find mapper for
    * @return an initialized instance of Shuffler_Mapper
    */
    function getMapper( $modelName = NULL )
    {
        if( $modelName == NULL )
        {
            $modelName = $this->modelName;
        }
        if( $modelName == $this->modelName )
        {
            return $this;
        }
        return Shuffler_Factory::getInstance()->getMapper( $modelName, $this->adapter );
    }
    
    /**
    * template method for manual initilization
    */
    protected function init(){}
    
    public function getModelName()
    {
        return $this->modelName;
    }
    
    /**
    * Checks if there is an existing mapping for this field
    * 
    * @return boolean
    */
    public function hasMappingFor( $field )
    {
        foreach( $this->getMappings() as $mapping )
        {
            if( $mapping->getField() == $field )
            {
                return true;
            }
        }
        return false;
    }
    
    /**
    * Add an embedded mapping
    * 
    * @param string name of field
    * @param array optional additional options
    * 
    * @return Shuffler_Mapper
    */
    public function addEmbedded( $field, array $options = array() )
    {
        if( !isset( $options['class'] ) )
        {
            $options['class'] = $this->getModelName();
        }
        $this->addMapping( $field, 'embedded', $options );
        return $this;
    }   
    
    /**
    * Add a single mapping
    * 
    * @param string name of Model to relate
    * @param array optional additional options
    * 
    * @return Shuffler_Mapper
    */
    public function addSingle( $modelName, array $options = array() )
    {
        if( !isset( $options['field'] ) )
        {
            if( $modelName == $this->getModelName() )
            {
                throw new Exception( 'trying to associate a class field to point to an object of the same class & no join field was provided' ); 
            }
            $options['field'] = $this->getMapper( $modelName )->getTableName() . '_id';
        }
        $options['class'] = $modelName;
        $this->addMapping( $options['field'], 'single', $options );
        return $this;
    }
    
    /**
    * Add an association table mapping
    * 
    * @param string name of Model to relate
    * @param array optional additional options
    */
    public function addAssociation( $modelName, array $options = array() )
    {
        if( !isset( $options['field'] ) )
        {
            $options['field'] = $this->getTableName() . '_id';
        }
        $options['class'] = $modelName;
        $this->addMapping( $options['field'], 'association', $options );
        return $this;
    } 
    
    /**
    * Add a collection mapping
    * 
    * @param string name of Model to relate
    * @param array optional additional options
    */
    public function addCollection( $modelName, array $options = array() )
    {
        if( !isset( $options['field'] ) )
        {
            $options['field'] = $this->getTableName() . '_id';
        }
        $options['class'] = $modelName;
        $this->addMapping( $options['field'], 'collection', $options );
        return $this;
    } 
    
    /**
    * Add a plugin mapping
    * 
    * @param string name of field
    * @param string name of Model to relate
    * @param array optional additional options
    */
    public function addPlugin( $field, $modelName, array $options = array() )
    {
        if( $this->getPlugin() !== false )
        {
            throw new Exception( 'Mappers may only have 1 plugin mapping' );
        }
        $this->addMapping( $field, 'plugin', $options );
        return $this;
    }    
    
    /**
    * Adds a mapping
    * 
    * @param mixed string name of field or mapping object
    * @param string name of class
    * @param string type of mapping
    * @param array additional options 
    *
    * @throws Exception
    */
    public function addMapping( $field, $type = 'Embedded', array $options = array() )
    {
        if( $field instanceof Shuffler_Mapper_Mapping )
        {
            array_push( $this->mappings, $field );
            return $this;
        }
        if( is_null( $field ) )
        {
            throw new Exception( 'Field NULL' );
        }
        if( !isset( $options['class'] ) )
        {
            $options['class'] = $this->modelName;
        }
        $mapping = $this->createMapping( $field, $type, $options );
        if( !$mapping instanceof Shuffler_Mapper_Mapping )
        {
            throw new Exception( 'Mapping must be instance of Shuffler_Mapper_Mapping '  );
        }
        array_push( $this->mappings, $mapping );
        return $this;
    }
    
    /**
    * Creates a mapping
    * 
    * @param string name of field
    * @param string name of class
    * @param string type of mapping
    * @param array additional options 
    * @return a populated Shuffler_Mapper_Mapping object
    */
    private function createMapping( $field, $type = 'Embedded', array $options = array() )
    {
        // some basic cleansing
        $type = ucwords( strtolower( $type ) );
        $className = 'Shuffler_Mapper_Mapping_' . $type;
        if( !class_exists( $className ) )
        {
            throw new Exception( 'Unrecognized mapping type ' . $className );
        }
        $options['field'] = $field;
        $mapping = new $className( $this, $options );
        return $mapping;    
    }
    
    /**
    * Get a collection of mappings for this datamapper
    * 
    * @return iterator for mappings
    */
    protected function getMappings()
    {
        return new ArrayIterator( $this->mappings );
    }
    
    /**
    * Get a collection of single valued mappings for this datamapper
    * 
    * @return iterator for single valued mappings
    */
    protected function getSingleMappings()
    {
        $return = array();
        foreach( $this->mappings as $mapping )
        {
            if( $mapping->getType() == 'single' )
            {
                array_push( $return, $mapping );
            }
        }
        return new ArrayIterator( $return );
    }
    
    /**
    * Get the name of the table this mapper uses, or looks up another model if passed
    * 
    * @param model name to check the mapper for
    */
    public function getTableName( $modelName = NULL )
    {
        if( is_null( $modelName ) )
        {
            return $this->tableName;
        }
        return $this->getMapper( $modelName )->getTableName();
    }
    
    public function getSchemaName()
    {
        return $this->schema;
    }
    
    /**
    * Finds and Get a collection of all models in the table
    * 
    * @return Shuffler_Collection of Shuffler_Model
    */
    public function findAll()
    { 
        return $this->query( $this->select() );
    }
    
    /**
    * Find the first ( or only ) model by one of it's record's fields
    * 
    * @param string name of field to constrain on
    * @param mixed value
    * 
    * @return mixed Shuffler_Model or false if none found
    */
    public function findOneByField( $fieldName, $value )
    {
        $rs = $this->findByField( $fieldName, $value );
        if( !$rs->count() )
        {
            return false;
        }
        return $rs->getModelAtIndex( 0 );
    }
    
    /**
    * Find row models where table field name matches given value
    * 
    * @param string Name of field
    * @param value to match
    * @return collection of Shuffler_Model
    */
    public function findByField( $fieldName, $value )
    {
        $db = $this->getReadAdapter();
        $where = $db->quoteInto( $db->quoteIdentifier( $fieldName ) . ' = ?', $value );
        return $this->findWhere( $where );
    }
    
    /**
    * Executes a query on this table with a given SQL WHERE clause
    * 
    * @param string SQL WHERE condition
    * @return a collection of Shuffler_Model
    */
    public function findWhere( $where )
    {                                 
        $args =  func_get_args();
        $select = $this->select();
        call_user_func_array( array( $select, 'where' ), $args );
        return $this->query( $select );   
    }
    
    /**
    * Executes a query and mapped loaded models
    * 
    * @param Zend_Db_Select query
    * @return a collection of Shuffler_Model
    */
    public function query( Zend_Db_Select $query )
    {
        $rs = $this->doQuery( $this->getReadAdapter(), $query )->fetchAll();
        // load all rows from the result set
        return $this->loadAll( $rs );    
    }
    
    /**
    * Executes a query on the given database adapter
    * 
    * @param a database adapter to use
    * @param query to run ( instance of Zend_Db_Select? )
    * @throws Exception
    */
    protected function doQuery( Shuffler_Mapper_Adapter $db, Zend_Db_Select $select )
    {
        try
        {
            $query = $db->query( $select );
            return $query;
        }
        catch( Exception $e )
        {
            throw new Exception( 'Unable to query: ' . $e->getMessage() );
        }
        return $this;
    }
    
    /**
    * Find a single model by it's primary key value
    * 
    * if the identity map already contains this object it is returned
    * 
    * @param primary key value
    * @return Shuffler_Model an initilized model
    * 
    * @todo tests for empty return
    */
    public function find( $id )
    { 
        $id = (int)$id;
        if( $model = $this->identityMap->get( $id ) )
        {
            return $model;
        }
        $pkName = $this->primaryKey();
        $select = $this->select()->where( $this->getTableName() . '.' . $pkName . ' = ?', $id );
        try
        {
            $rs = $this->doQuery( $this->getReadAdapter(), $select )->fetch();
        }
        catch( Exception $e )
        {
            throw new Exception( 'Unable to run Zend_Query: ' . $e->getMessage() );
        }
        if( empty( $rs ) )
        {
            return Shuffler_Factory::createNullModel( $this->modelName );
        }
        $this->createIdentityPlaceholder( $id );
        $tmp = $this->identityMap->get( $id );
        $model = $this->load( $id, $rs );
        return $model;
    }

    public function findExcluding( Shuffler_Model $type )
    {
        return $this->findWhere( $this->primaryKey() . ' != ' . (int)$type->getId() );
    }
    
    /**
    * Create a ghost object in the identity map to avoid cyclic references   
    * 
    * @param int id of model to create identity for
    */
    protected function createIdentityPlaceholder( $id )
    {
        $this->identityMap->add( $this->createNullModel( $id ) );   
    }
    
    /**
    * Persist domain model, Delegates to either update(),
    * or insert() if no identifier is present
    *
    * @param Shuffler_Model to persist
    * @throws Exception
    */
    public function save( Shuffler_Model $model )
    {
        if( !( $model instanceof $this->modelName ) )
        {
            throw new Exception( $this->modelName . ' expected, ' . get_class( $model ) . ' given' );
        }
        $this->preSave( $model );
        $action = $model->getId() ? 'update' : 'insert';
        $this->$action( $model );
        $id = $model->getId();
        if( $id == 0 )
        {
            $id = (int)$this->getWriteAdapter()->lastInsertId();
            $model->setId( $id );
        }
        foreach( $this->getMappings() as $mapping )
        {
            $mapping->save( $model );
        }
        $this->postSave( $model );
        return $id;
    }
    
    /**
    * template method for hooking save
    * 
    * @param Shuffler_Model that is being saved
    */
    protected function preSave( Shuffler_Model $model )
    {
    
    }
    
    /**
    * template method for hooking save
    * 
    * @param Shuffler_Model that is being saved
    */
    protected function postSave( Shuffler_Model $model )
    {
        $this->doSave( $model );
    }
    
    /**
    * template method for hooking save
    * 
    * @param Shuffler_Model that is being saved
    * @deprecated
    */
    protected function doSave( Shuffler_Model $model )
    {
    
    }
    
    /**
    * Delete a domain model
    * 
    * @param Shuffler_Model to delete
    */
    public function delete( Shuffler_Model $model )
    {
        $db = $this->getWriteAdapter();
        $db->delete(
            array( 'a' => $this->tableName ),
            $db->quoteInto( $db->quoteIdentifier( $this->primaryKey() ) . ' = ?', $model->getId() )
        );
        $this->doDelete( $model );
        return $this;
    }
    
    protected function doDelete( Shuffler_Model $model )
    {
    }
    
    /**
    * Looks up a mapping for a field, attempts to guess mapping based on field naming
    * 
    * @Get String of mapping name, false on failure
    */
    protected function getMapping( $field )
    {
        foreach( $this->getMappings() as $mapping )
        {
            if( $mapping->getField() == $field )
            {
                return $mapping;
            }
        }
        // no mapping found
        return false;
    }
    
    /**
    * Clear all models currently loaded in identity map
    */
    public function clearIdentityMap()
    {
        $this->identityMap = new Shuffler_Mapper_IdentityMap();
        return $this;
    }
    
    /**
    * Creates a null object for a given identifier, defaults to 0
    * 
    * @param optional integer for id
    * @return an instance of Shuffler_Model that implements Shuffler_Null
    * @throws Exception
    */
    public function createNullModel( $id = 0 )
    {
        return Shuffler_Factory::createNullModel( $this->getModelName(), $id );
    }
    
    /**
    * Get immutable mappings
    * 
    * @return array
    */
    protected function getImmutableMappings()
    {
        $return = array();
        foreach( $this->getMappings() as $mapping )
        {
            if( $mapping->isImmutable() )
            {
                $return[] = $mapping;
            }
        }
        return $return;
    }
    
    /**
    * Get collection mappings
    * 
    * @return array
    */
    protected function getCollectionMappings()
    {
        $return = array();
        foreach( $this->getMappings() as $mapping )
        {
            if( $mapping->getType() == 'collection' || $mapping->getType() == 'association' )
            {
                $return[] = $mapping;
            }
        }
        return $return;
    }
    
    /**
    * Default loading strategy, delegates loading of each object property to a mapping object
    * 
    * @param primary key value
    * @param result set
    * @return populated model
    */
    protected function doLoad( $id, $rs )
    {
        $model = $this->createModel( $id, $rs );
        foreach( $this->getMappings() as $mapping )
        {
            $model = $mapping->load( $model, $rs );
            if( !$model instanceof Shuffler_Model )
            {
                throw new Exception( get_class( $mapping ) . ' did not return a Shuffler_Model'  );
            }
        }
        $model = $this->postLoad( $model, $id, $rs );
        return $model;   
    }  
    
    protected function postLoad( $model, $id, $rs )
    {
        return $model;
    }
    
    /**
    *  Create a model ( loads immutable data )
    * 
    * @return Shuffler_Model
    */
    protected function createModel( $id, $rs, $modelName = '' )
    {       
        $model = $this->doCreateModel( $id, $rs, $modelName );
        if( !$model instanceof Shuffler_Model )
        {
            throw new Exception( 'Model must be instance of Shuffler_Model, ' . gettype( $model ) . ' given' );
        }
        return $model;
    }
    
    /**
    * Loads immutable data into the model's constructor if it is mapped as so
    * 
    * Immutable fields means the values are passed thru the constructor ( no setters exist )
    * 
    * @return Shuffler_Model
    */
    protected function doCreateModel( $id, $rs, $modelName = '' )
    {
        $args = array();
        $modelName = $modelName ? $modelName : $this->getModelName();
        if( $mapping = $this->getPlugin() )
        {
            $modelName = $mapping->getModelName( $rs );
        }
        if( $this->hasPrimaryKey() )
        {
            $args = $this->getImmutableValues( $rs );
        }
        
        
        if( !class_exists( $modelName ) )
        {
            throw new Exception( 'Model class doesn\'t exist \'' . $modelName . '\'' );       
        }
        $reflectionObj = new ReflectionClass( $modelName );
        if( !class_exists( $modelName ) )
        {
            throw new Exception( 'Model doesnt exist' ); 
        }
        class_exists( $modelName );
        return $reflectionObj->newInstanceArgs( $args );
    }
    
    /**
    * Get an array of immutable mapping's values from a result set
    * 
    * @param array associative array row
    * @return array of string values
    */
    protected function getImmutableValues( $row )
    {
        $values = array();  
        foreach( $this->getImmutableMappings() as $mapping )
        {     
            $value = $mapping->getValue( $row );
            // pass actual integers instead of numerical strings
            if( is_numeric( $value ) )
            {
                $value = (int)$value;
            }
            $values[] = $value;
        }
        return $values;
    }
    
    /**
    * Get the first plugin mapping if one exists, otherwise returns false
    * 
    * @return mixed
    */
    protected function getPlugin()
    {
        foreach( $this->getMappings() as $mapping )
        {
            if( $mapping->getType() == 'plugin' )
            {
                return $mapping;
            }
        }
        return false;
    }
    
    /**
    * get list of fields for this model's table, minus the primary key
    * 
    * @return array of fields
    */
    public function getFields()
    {
        try
        {
            $fields = $this->getReadAdapter()->describeTable( $this->tableName, $this->schema );
        }
        catch( Exception $e )
        {
            throw new Exception( 'Unable to describe table: ' . $e->getMessage() );
        }
        
        $fields = array_keys( $fields );
        
        return $fields;
    }
    
    public function getSelectCols()
    {
        $fields = array();
        foreach(  $this->getFields() as $k => $field )
        {
            $fields[ $this->getTableName() . '_' . $field ] = $field;
        }
        return $fields;
    }
    
    /**
    * get an array of field values that belong to the main table, excluding the primary key
    * Basically single valued forein keys ( where this main table owns the id ), or embedded values,
    * association & collection mappings, etc... all will handle their own persistence
    * 
    * for single valued mappings that have not been saved it will return their current id, which is always 0,
    * if a non-zero value is needed, for instance when inserting or updating the row using these values, all single value mappings must
    * first be saved, otherwise they will just return a 0 value since they have not been assigned an id yet.
    * 
    * @return array of string values
    */
    public function getFieldValues( Shuffler_Model $model )
    {
        $values = array();
        foreach( $this->getMappings() as $mapping )
        {
            if( $mapping->fieldIsInline() && $mapping->getField() !== $this->primaryKey()  )
            {
                $field = $mapping->getField();
                $values[ $field ] = $mapping->getFieldValue( $model );
            }
        }
        return $this->doGetFieldValues( $model, $values );
    }
    
    protected function doGetFieldValues( $model, $values )
    {
        return $values;
    }
    
    /**
    * Pulls values out of model and runs an insert query on the table(s)
    * 
    * @throws Exception
    */
    protected function insert( Shuffler_Model $model )
    {
        $values = $this->getFieldValues( $model );
        try
        {
            $db = $this->getWriteAdapter();    
            $db->insert( array( 'a' => $this->tableName ), $values );
        }
        catch( Exception $e )
        {
            throw new Exception( 'Unable to insert: ' . $e->getMessage() . ' thrown in mapper for domain model [' . $this->modelName . ']' );
        }
        $id = (int)$this->getWriteAdapter()->lastInsertId(); 
        $this->doInsert( $id, $model );
        return $this;     
    }
    
    protected function doInsert( $id, Shuffler_Model $model ) {}
    
    /**
    * Pulls values out of model and runs an update query on the table(s)
    * 
    * @throws Exception
    */
    protected function update( Shuffler_Model $model )
    {
        $values = $this->getFieldValues( $model );
        try
        {
            $db = $this->getWriteAdapter();
            if( $this->schema )
            {
                $table = $this->schema . '.' . $this->getTableName();
            }
            else
            {
                $table = $this->getTableName();
            }
            $db->update( $table, $values, $this->primaryKey() . ' = ' . $model->getId() );
        }
        catch( Exception $e )
        {
            throw new Exception( 'Unable to update: ' . $e->getMessage() );
        }
        $id = (int)$this->getWriteAdapter()->lastInsertId(); 
        $this->doUpdate( $id, $model );
        return $this;
    }
    
    protected function doUpdate( $id, Shuffler_Model $model ) {}
    
    /**
    * Builds a base select statement,
    * complete with the specific fields to select
    * 
    * @return Zend_Db_Select
    */
    public function select()
    {
        $select = $this->doSelect();
        $select = $this->doSelectFields( $select );
        return $select;
    }  
    
    /**
    * Builds a base select statement with no columns / select part
    * used for adding on aggregrate / other "sugar"
    * 
    * @return Zend_Db_Select   
    */
    protected function doSelect()
    {
        $db = $this->getReadAdapter();
        $select = $db->select();
        
        $select->from( $this->tableName, NULL, $this->schema );
        return $select;
    } 
    
    
    public function countAll( )
    {
        return $this->countWhere( '1' );
    }
    
    /**
    * Get a count of rows for a given where condition
    * 
    * @return integer row count
    */
    public function countWhere()
    {
        $select = $this->doSelect();
        $args =  func_get_args();
        call_user_func_array( array( $select, 'where' ), $args );
        return $this->countSelect( $select );
    }
    
    /**
    * Count records that have a given value for a specified field
    */
    public function countByField( $fieldName, $value )                          
    {
        $db = $this->getReadAdapter();
        $where = $db->quoteInto( $db->quoteIdentifier( $fieldName ) . ' = ?', $value );
        return $this->countWhere( $where );
    }
    
    public function countSelect( Zend_Db_Select $select )
    {
        return $this->selectCols( 'count(*)', $select );
    }
    
    public function selectCols( $cols, Zend_Db_Select $select )
    {
        $select->columns( $cols );
        try
        {  
            $result = $this->getReadAdapter()->query( $select );
        }
        catch( Exception $e )
        {
            throw new Exception( 'problem with SQL ' . (string)$select . ' ' . $e->getMessage() );
        }
        $result->setFetchMode(Zend_Db::FETCH_NUM);
        $result = $result->fetch();
        if( count( $result ) > 1 )
        {
            throw new Exception( 'unexpected columns in query' );
        }
        return $result[0];
    }
    
    protected function doSelectFields( Zend_Db_Select $select )
    {
        $fields = $this->getFields();
        foreach( $fields as $k => $field )
        {
            $fields[ $this->getTableName() . '_' . $field ] = $field;
            unset( $fields[$k] ); // unset numeric index
        }
        $select->columns( $fields ); 
        foreach( $this->getMappings() as $mapping )
        {
            $select = $mapping->select( $select );
        }
        return $select;
    }

    /**
    * Load a row without advancing the resultset pointer ( for mapping joined resultsets )
    * 
    * @param primary key value
    * @param row object ( stdClass )
    */
    public function loadRow( $id, $rs )
    {
        foreach( $this->getMappings() as $mapping )
        {
            $field = $mapping->getField();
            if( isset( $rs[ $field ] ) )
            {
                return $this->load( $id, $rs );
            }
            return $this->find( $id );
        }
    }
    
    /**
    * Handles loading for a given row, defers to strategy
    * 
    * @param primary key value
    * @param row object ( stdClass )
    */
    public function load( $id, $rs )
    {
        if( !$rs )
        {
            throw new Exception( 'result set not passed' ); 
        }
        if( $model = $this->identityMap->get( $id ) )
        {
            if( !( $model instanceof Shuffler_Null ) )
            {
                return $model;
            }
        }
        $model = $this->doLoad( $id, $rs );
        $this->identityMap->add( $model );
        return $model;
    }
    
    /**
    * Loads a rowset
    * 
    * @param rowset to iterate
    * @return Shuffler_Collection
    */
    public function loadAll( array $rs )
    {
        $result = new Shuffler_Collection();
        foreach( $rs as $row )
        {    
            $pk = 0;  
            if( $this->hasPrimaryKey() )
            {
                $pkName = $this->getTableName() . '_' . $this->primaryKey();
                $pk = $row[ $pkName ]; 
                if( !$pk )
                {
                    throw new Exception( 'no primary key in result set, expected "' . $pkName . '"; table was ' . $this->getTableName() );
                }
            }
            $model = $this->load( $pk, $row );
            if( !$result->contains( $model ) )
            {
                $result->addModel( $model );
            }
        }
        return $result;
    }
    
    public function hasMappings()
    {
        return count( $this->getMappings() ) > 0;
    }
    
    public function hasPrimarykey()
    {
        return $this->primaryKey != NULL;
    }
    
    /**
    * get name of the primary key for this mapper's table
    */
    public function primaryKey()
    {
        return $this->primaryKey;
    }
    public function getReadAdapter()
    {
        return $this->adapter;
    }
    
    public function getWriteAdapter()
    {
        return $this->adapter;
    }                                                              

    /**
    * Set the table name for this mapper
    * 
    * @param string name of table this mapper maps for
    */
    public function setTable( $table )
    {
        $this->tableName = $table;
    }
}